package NG::Chat::Login;
use strict;
use warnings;
use utf8;

use EV;
use Time::HiRes qw(gettimeofday);
use Digest::MD5 qw(md5_hex);
use BerkeleyDB;
use JSON::XS;

#use NG::Chat::Util;
use NG::Sys::Text;


sub Create {
	my $app = shift;
	my %obj = (app => $app);
	# список клиентов по логинам
	$obj{logins} = {};
	# список клиентов по сессиям
	$obj{sids} = {};
	$obj{admin_pass} = $app->{admin_pass};
	$obj{db_login} = new BerkeleyDB::Btree
		-Filename => $app->{cfg}{path}{var}."/".$app->{db_filename}, 
		-Flags => DB_CREATE;;
	return bless \%obj,__PACKAGE__;
}
sub cleanup {
	my $self = shift;
	delete $self->{app};
	delete $self->{logins};
	delete $self->{sids};
	$self->{db_login}->db_sync;
	delete $self->{db_login};
	$self->{free} = 1;
}
sub get_login {
	my ($self,$login) = @_;
	my $rec;
	if($login and $self->{db_login}->db_get($login,$rec)!= DB_NOTFOUND){
		return decode_json($rec);
	}else{
		return $rec;
	}
}

sub create_login {
	my ($self,$login,$pass,$tmp,$chanel) = @_;
	my %rec = (
		l => $login,
		p => $pass,
		r => time(),
		t => ($tmp ? 1:0),
	);
	if($chanel && $self->{app}{'chat-chanel'}->get_chanel($chanel)){
		$rec{c} = $chanel;
	}
	my $err;
	if($err = validate_login($login,$tmp)){
		return $err;
	}elsif($err = validate_pass($pass,$tmp)){
		return $err;
	}elsif($self->{db_login}->db_put($login,encode_json(\%rec),DB_NOOVERWRITE) == DB_KEYEXIST){
		return {error => 409,text => "login [$login] exists"};
	}else{
		$self->{db_login}->db_sync;
		$self->{app}{'chat-command'}->{logger}->send("<reg> [$login] tmp".($tmp ? 1:0));
		return 0;
	}
}
sub remove_login {
	my ($self,$login) = @_;
	if($self->{db_login}->db_del($login) == DB_NOTFOUND){
		return {error => 409,text => "login [$login] not exists"};
	}else{
		$self->{db_login}->db_sync;
		clear_session($login);
		$self->{app}{'chat-command'}->{logger}->send("<unreg> [$login]");
		return 0;
	}
}
sub change_login {
	my ($self,$login,$newlogin,$chanel) = @_;

	if(my $err = validate_login($newlogin)){
		return $err;
	}

	if(my $rec = get_login($self,$login)){
		$rec->{l} = $newlogin;
		$rec->{t} = 0;
		if($chanel){
			$rec->{c} = $chanel;
		}
		$rec = encode_json($rec);
		if($self->{db_login}->db_put($newlogin,$rec,DB_NOOVERWRITE) == DB_KEYEXIST){
			return {error => 409,text => "login [$newlogin] exists"};
		}
		$self->{db_login}->db_del($login);
		$self->{db_login}->db_sync;
	
		if($self->{logins}{$login}){
			clear_session($self,$login);
		}
		$self->{app}{'chat-command'}->{logger}->send("<rename> [$login] to [$newlogin]");
		return 0;
	}else{
		return {error=>404,text=>"login [$login] nof found"};
	}
}
sub change_pass {
	my ($self,$login,$newpass) = @_;

	if(my $err = validate_pass($newpass)){
		return $err;
	}

	if(my $rec = get_login($self,$login)){
		$rec->{p} = $newpass;
		$rec = encode_json($rec);
		$self->{db_login}->db_put($login,$rec);
		$self->{db_login}->db_sync;

		if(my $lgn = $self->{logins}{$login}){
			@{$lgn}{qw(pass tmp)} = ($newpass,0);
		}

		return 0;
	}else{
		return {error=>404,text=>"login [$login] nof found"};
	}
}
sub change_chanel {
	my ($self,$login,$newchanel) = @_;
	
	if($newchanel && $self->{app}{'chat-chanel'}->get_chanel($newchanel)){
		return +{error=>404 ,text=> "bad chanel"};
	}

	if(my $lgn = $self->{logins}{$login}){
		@{$lgn}{c} = $newchanel;
	}
	my $rec = get_login($self,$login);
	$rec->{c} = $newchanel;
	$rec = encode_json($rec);
	$self->{db_login}->db_put($login,$rec);
	$self->{db_login}->db_sync;
	return 0;
}
sub info_login {
	my ($self,$login) = @_;

	unless($login){
		return {error=>412,text=>"need login"};
	}elsif(my $rec = get_login($self,$login)){
		return {type => "data",data => $rec};
	}else{
		return {error=>404,text=>"login [$login] nof found"};
	}
}

sub enter_login {
	my ($self,$login,$pass,$fh,$sender) = @_;
	unless($login){
		return {error=>400, text=>"need login",	};
	}
	unless($pass){
		return {error=>400, text=>"need password",};
	}

	my $sid = generate_sid();
	my $now = time();
	my $lgn;
	if($lgn = $self->{logins}{$login}){
		if($login =~ /^admin/){
			if($pass eq $self->{admin_pass}){
				$pass = "";
				$lgn->{title} = "admin";
				$lgn->{nolist} = 1;
			}
		}
		if($lgn->{pass} eq $pass){
			clear_session($self,$login,$sid);
		}else{
			return {error=>412,text=>"bad password",};
		}
	}else{
		my $rec;
		my $title;
		my $nolist;
		if($login =~ /^admin/){
			$rec->{p} = "";
			$title = "admin";
			$nolist = 1;
			if($pass eq $self->{admin_pass}){
				$pass = "";
			}
		}else{
			unless($rec = get_login($self,$login)){
				return {error=>404,	text=>"bad login",};
			}
		}
		if($rec->{p} eq $pass){
			if($rec->{r}){
				$rec->{o} = 0;
				$rec->{i} = $now;
				$self->{db_login}->db_put($login,encode_json($rec));
				$self->{db_login}->db_sync;
			}
		}else{
			return {error=>401,	text=>"bad password",};
		}
		$lgn = $self->{logins}{$login} = {
			sid => $sid,
			private => [],
			chanels => {},
			pass => $rec->{p},
			bc => $self->{app}{'chat-command'}{iBroadCast},
			tmp => $rec->{t},
			c => $rec->{c},
			title => $title || $login,
			nolist => $nolist,
		};
		if($rec->{t}){
			$lgn->{title} =  "Гость";
			$lgn->{nolist} = 1;
		}
	}
	$lgn->{topic} = 0;

	$self->{sids}{$sid} = {login => $login,start => $now,last => $now,};

	if(my $ttl = $self->{app}{session_ttl}){
		my $w = EV::timer $ttl,$ttl,\&check_logout;
		$w->data([$login,$self]);
		@{$self->{sids}{$sid}}{('check','fh')} = ($w,$fh);
	}

	my $c = $lgn->{c} || "default";
	my $ct = $self->{app}{'chat-chanel'}->join_login($login,$c);

	$self->{app}{'chat-command'}->{logger}->send("<login> [$login] tmp".($lgn->{tmp}||0));
	return +{type => "notice", param1 => "welcome",param2 => ($lgn->{tmp}||0), param3 => $login,
			 sid => $sid, chanel => $c, chaneltitle => $ct, title => $lgn->{title}};
}
sub generate_sid {
	return md5_hex(gettimeofday(),rand());
}
sub clear_session {
	my $self = shift;
	my $login = shift;
	my $new_sid = shift || "";
	my $lgn = $self->{logins}{$login};
	for my $ch (keys %{$lgn->{chanels}}){
		$self->{app}{'chat-chanel'}->split_login($login,$ch,$new_sid);
	}
	$lgn->{chanels} = {};
	if(my $old_sid = $lgn->{sid}){
		my $os = delete $self->{sids}{$old_sid};
		$os->{check} = undef;
		if($os->{fh} && $os->{fh}->opened){
			$os->{fh}->close;
		}
	}
	if($new_sid){
		$lgn->{sid} = $new_sid;
	}else{
		delete $self->{logins}{$login};
	}
	$self->{app}{'chat-command'}->{logger}->send("<clear> [$login]");
	delete $self->{app}{'chat-command'}{waiting}{$login};
}

sub check_session {
	my $self = shift;
	my $msg = shift;
	unless(my $sid = $msg->{sid}){
		if((my $login = $msg->{login}) && $msg->{pass}){
			if($login =~ /^admin/){
				if($msg->{pass} eq $self->{admin_pass}){
					$msg->{title} = "admin";
					return $login;
				}
			}
		}
		return undef;
	}elsif(my $s = $self->{sids}{$sid}){
		my $lgn = $self->{logins}{$s->{login}};
		if($lgn){
			$msg->{title} = $lgn->{title};
		}
		my $now = time();
		my $ttl = $self->{app}{session_ttl};
		if($ttl && ($s->{last} + $ttl < $now)){
			if($lgn){
				if($lgn->{sid} eq $sid){
					clear_session($self,$s->{login});
				}
			}
			delete $self->{sids}{$sid};
			return undef;
		}else{
			if($s->{check}){
				$s->{check}->again;
			}
			$s->{last} = $now;
			return $s->{login};
		}
	}else{
		return undef;
	}
}
sub check_logout {
	my $w = shift;
	my ($login,$self) = @{$w->data("")};
	if($login && $self && !$self->{free}){
		if($login =~ /^admin/){
			clear_session($self,$login);
		}
		elsif(my $rec = get_login($self,$login)){
			clear_session($self,$login);
			if($rec->{t}){
				$self->{app}{'chat-command'}->{logger}->send("<unreg tmp> [$login]");
				$self->{db_login}->db_del($login);
			}else{
				$self->{app}{'chat-command'}->{logger}->send("<logout> [$login]");
				$rec->{o} = time();
				$rec = encode_json($rec);
				$self->{db_login}->db_put($login,$rec);
			}
			$self->{db_login}->db_sync;
		}
	}
}
sub can_reg {
	my ($self,$login) = @_;
	return $login =~ /^admin/;
}
sub can_private {
	my ($self,$login) = @_;
	my $lgn;
	if($login && ($lgn = $self->{logins}{$login}) && !$lgn->{tmp}){
		return 1;
	}else{
		return undef;
	}
}
sub validate_login {
	my ($login,$tmp) = @_;
	if($tmp){
		return 0;
	}elsif(my $err = check_login_valid($login)){
		return {error =>412,text=>$err};
	}
	return 0;
}
sub validate_pass {
	my ($pass,$tmp) = @_;
	if($tmp){
		return 0;
	}elsif(!$pass){
		return {error =>412,text=>"empty pass"};
	}
	return 0;
}
sub check_wipe {
	my ($self,$login) = @_;
	my $ret = 0;
	if($login =~/^admin/){
		return $ret;
	}
	elsif(my $lgn = $self->{logins}{$login}){
		my $now = gettimeofday();
		if($lgn->{lm}){
			my $diff = $lgn->{wipe} * 0.5;
			if($now - $lgn->{lm} < $diff){
				$ret = $diff;
				$lgn->{wipe}++;
			}else{
				$lgn->{wipe} = 1;
			}
		}else{
			$lgn->{wipe} = 1;
		}
		$lgn->{lm} = $now;
	}
	return $ret;
}
1;
